//----------------------------------------------------------------------------
//  ,-.
// /_\___ ,   Ninja Framework
// '  ___\    (C) Riccardo Capra - http://riccar.do
//  //   \\
//----------------------------------------------------------------------------
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//----------------------------------------------------------------------------

#include "NjExample01.h"

#if NJ_PLATFORM_OS_LINUX
#	include "NjThreadsSystemLinux.h"

	//----------------------------------------------------------------------------
	class NjThreadsSystemLinux::Static
	{
	public:
		struct Data
		{
			ThreadFunct m_pfnThread;
			void * m_pThreadData;
		};

		static void * ThreadProc(void * pParameter);
		static NjBool ToLinuxTime(TimeMs uTimeMs, timespec & kTime);

	private:
		NJ_DISABLE_INSTANCE(Static);
	};
	//----------------------------------------------------------------------------
	NjThreadsSystemLinux::Thread * NjThreadsSystemLinux::CreateThread(ThreadFunct pfnThread, void * pData)
	{
		pthread_t * const pThreadImpl(NjNew(pthread_t));

		Static::Data * pParameter(NjNew(Static::Data));
		pParameter->m_pfnThread=pfnThread;
		pParameter->m_pThreadData=pData;

		if (pthread_create(pThreadImpl, NJ_NULL, Static::ThreadProc, pParameter)!=0)
		{
			NjDelete(pParameter);
			NjDelete(pThreadImpl);
			return NJ_NULL;
		}

		return reinterpret_cast<NjThreadsSystemLinux::Thread *>(pThreadImpl);
	}
	//----------------------------------------------------------------------------
	NjThreadsSystemLinux::TryResult NjThreadsSystemLinux::TryJoinThread(Thread * pThread, TimeMs uTimeMs)
	{
		if (pThread==NJ_NULL)
			return NjEventsSystem::TRY_FAILURE;

		pthread_t * const pThreadImpl(reinterpret_cast<pthread_t *>(pThread));
		
		if (uTimeMs==NjEventsSystem::WAIT_FOREVER)
		{
			NjInt const iResult(pthread_join(*pThreadImpl, NJ_NULL));

			if (iResult==0 || iResult==ESRCH)
				return NjEventsSystem::TRY_SUCCESS;
		
			return NjEventsSystem::TRY_FAILURE;
		}
		
		timespec kTime;
		if (!Static::ToLinuxTime(uTimeMs, kTime))
			return NjEventsSystem::TRY_FAILURE;

		NjInt const iResult(pthread_timedjoin_np(*pThreadImpl, NJ_NULL, &kTime));

		if (iResult==0)
			return NjEventsSystem::TRY_SUCCESS;
		
		if (iResult==ETIMEDOUT)
			return NjEventsSystem::TRY_TIMEOUT;
		
		return NjEventsSystem::TRY_FAILURE;
	}
	//----------------------------------------------------------------------------
	NjBool NjThreadsSystemLinux::DestroyThread(Thread * pThread)
	{
		if (pThread==NJ_NULL)
			return NJ_FALSE;

		pthread_t * const pThreadImpl(reinterpret_cast<pthread_t *>(pThread));
		NjInt iResult(pthread_cancel(*pThreadImpl));
		NjDelete(pThreadImpl);

		return iResult==0 || iResult==ESRCH;
	}
	//----------------------------------------------------------------------------
	NjBool NjThreadsSystemLinux::Sleep(TimeMs uTimeMs)
	{
		return usleep(useconds_t(uTimeMs)*1000)==0;
	}
	//----------------------------------------------------------------------------
	NjThreadsSystem::YeldResult NjThreadsSystemLinux::Yeld()
	{
		if (pthread_yield()!=0)
			return NjThreadsSystem::YELD_SWITCHED_SUCCESS;

		return NjThreadsSystem::YELD_FAILURE;
	}
	//----------------------------------------------------------------------------
	void * NjThreadsSystemLinux::Static::ThreadProc(void * pParameter)
	{
		if (pParameter==NJ_NULL)
			return NJ_NULL;

		Data * const pData(reinterpret_cast<Data *>(pParameter));

		ThreadFunct pfnThread(pData->m_pfnThread);
		void * pThreadData(pData->m_pThreadData);
		NjDelete(pData);

		pfnThread(pThreadData);

		return NJ_NULL;
	}
	//----------------------------------------------------------------------------
	inline NjBool NjThreadsSystemLinux::Static::ToLinuxTime(TimeMs uTimeMs, timespec & kTime)
	{
		if (clock_gettime(CLOCK_REALTIME, &kTime)!=0)
			return NJ_FALSE;
		
		kTime.tv_sec+=time_t(uTimeMs/1000);
		kTime.tv_nsec=long(uTimeMs%1000)*1000000;
		
		if (kTime.tv_nsec>1000000000)
		{
			kTime.tv_nsec-=1000000000;
			kTime.tv_sec+=1;
		}

		return NJ_TRUE;
	}
	//----------------------------------------------------------------------------

#endif // NJ_PLATFORM_OS_LINUX
